﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Thallo.Configuration;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
using Thallo.Extention;
using System.IO;
namespace Thallo {
    public sealed class TypeDescriptorProvider {

        IUnityContainer _container;

        internal TypeDescriptorProvider() {
            _container = new UnityContainer();
        }

        internal TypeDescriptorProvider(IUnityContainer container) {
            _container = container;
        }

        internal TypeDescriptor Create(Type type, TypeElement typeSection = null, Thallo.Configuration.AssemblyElement ae = null) {
            var atr = type.GetCustomAttributes(typeof(ServiceCachedableAttribute), false).FirstOrDefault() as ServiceCachedableAttribute;
            var info = new TypeDescriptor();
            info.Disabled = true;
            info.Name = type.Name;
            info.Handle = type.TypeHandle;

            if (atr != null)
                info.Disabled = atr.Disabled;
            if (typeSection != null)
                info.Disabled = false;

            var flag = BindingFlags.Instance | BindingFlags.Public;
            IEnumerable<MethodElement> mes = null;
            if (typeSection != null)
                mes = typeSection.Cast<MethodElement>();
            UpdateMode mode = UpdateMode.None;
            if (ae != null && ae.Mode == "auto")
                mode = UpdateMode.Auto;

            if (typeSection != null) {
                if (typeSection.Mode == "auto" || typeSection.Mode == "normal")
                    mode = (UpdateMode)Enum.Parse(typeof(UpdateMode), typeSection.Mode, true);
            }

            info.Methods = type.GetMethods(flag).Select(mb => {
                var m = GetMethodInfo(mb);
                if (mode == UpdateMode.None)
                    m.UpdateMode = mode;
                MethodElement me = mes == null ? null : mes.FirstOrDefault(c => string.Compare(c.Name, m.Name, true) == 0);
                if (me != null) {
                    string[] groups = m.DependencyGroups;
                    if (!string.IsNullOrEmpty(me.Mode))
                        m.UpdateMode = (UpdateMode)Enum.Parse(typeof(UpdateMode), me.Mode, true);

                    m.ExpirationTime = me.ExpirationTime;
                    var handler = string.IsNullOrEmpty(me.Handler) ? null : new MethodHandler(Type.GetType(me.Handler));
                    m.MethodHandler = handler ?? m.MethodHandler;
                    m.DependencyAttributes = GetDependenciesFromConfig(mb, me, groups);
                }
                return m;
            }).ToArray();
            return info;
        }

        private DependencyAttribute[] GetDependenciesFromConfig(MethodBase mi, MethodElement me, string[] groups) {
            List<DependencyAttribute> lst = new List<DependencyAttribute>();

            if (me != null) {

                IEnumerable<DependencyElement> q = null;
                q = from op in me.Cast<DependencyElement>()
                    select op;

                if (!string.IsNullOrEmpty(me.DependencyGroup)) {
                    var g = ThalloSection.Current.DependencyGroups.Cast<DependencyGroup>()
                            .Where(c => me.DependencyGroup.Equals(c.Name)).SelectMany(c => c.Cast<DependencyElement>());
                    q = q.Union(from c in g.Cast<DependencyElement>() select c,
                                     new DependencyElementComparer());
                }
                if (groups != null && groups.Length > 0) {
                    var g = ThalloSection.Current.DependencyGroups.Cast<DependencyGroup>()
                            .Where(c => groups.Contains(c.Name)).SelectMany(c => c.Cast<DependencyElement>());
                    q = q.Union(from c in g.Cast<DependencyElement>() select c,
                                     new DependencyElementComparer());
                }

                foreach (var item in q) {
                    DependencyAttribute op = new DependencyAttribute(item.Type, item.Method);
                    if (op.Type == null)
                        op.Type = mi.DeclaringType;
                    if (!string.IsNullOrWhiteSpace(item.ParseType))
                        op.MethodHandlerType = Type.GetType(item.ParseType);
                    lst.Add(op);

                }
            }
            return lst.ToArray();
        }

        private Thallo.MethodDescriptor GetMethodInfo(System.Reflection.MethodInfo mb) {
            var m = new Thallo.MethodDescriptor();
            m.Method = mb;
            m.Name = mb.Name;
            m.UpdateMode = UpdateMode.Auto;
            var atr = mb.GetCustomAttributes(typeof(CachedableAttribute), false).FirstOrDefault() as CachedableAttribute;
            if (atr != null) {
                m.UpdateMode = UpdateMode.Normal;
                if (!string.IsNullOrEmpty(atr.DependencyGroup))
                    m.DependencyGroups = atr.DependencyGroup.Split(',');
                m.ExpirationTime = atr.ExpirationTime;
                m.MethodHandler = atr.MethodHandlerType != null ? new MethodHandler(atr.MethodHandlerType) : null;
                m.DependencyAttributes = (from c in mb.GetCustomAttributes(typeof(DependencyAttribute), false).Cast<DependencyAttribute>()
                                          select c).ToArray();
            }
            return m;
        }

        #region op methods
        public T Wrap<T>(T instance) where T : class {
            return _container.BuildUp<T>(instance);
        }

        public object Wrap(object instance) {
            if (instance == null)
                return null;
            var type = instance.GetType();
            return _container.BuildUp(type, instance);
        }

        public bool IsRegist(Type type) {
            return true;
        }

        public bool IsRegist(Assembly assembly) {
            return true;
        }

        public void Regist(Assembly assembly) {
            Regist(assembly, null);
        }

        private void Regist(Assembly assembly, Thallo.Configuration.AssemblyElement ae) {
            var types = assembly.GetTypes().Where(c => c.IsInterface);
            Loger.TraceInformation("Thallo regist assembly " + assembly.FullName);

            foreach (var item in types) {
                TypeElement te = null;
                if (ae != null)
                    te = ae.Cast<TypeElement>().FirstOrDefault(c => c.Name == item.FullName);
                Regist(item, te, ae);
            }

        }

        public void Regist(Type type) {
            Regist(type, null, null);
        }

        public void Remove(Assembly assembly) {
            throw new NotImplementedException();
        }

        public void Remove(Type type) {
            throw new NotImplementedException();
        }

        public void Setup(string file) {
            var config = new Microsoft.Practices.EnterpriseLibrary.Common.Configuration.FileConfigurationSource(file);
            var section = config.GetSection(ThalloSection.SECTION_NAME) as ThalloSection;
            Setup(section);
        }

        public void Setup(ThalloSection section) {
            var elements = section.Assemblies;
            var path = Helper.GetExcutePath();
            foreach (Thallo.Configuration.AssemblyElement item in elements) {
                if (string.Compare(item.Mode, "None", true) == 0)
                    continue;
                var file = Path.Combine(path, item.Name + ".dll");
                if (!File.Exists(file))
                    throw new FileNotFoundException(file);
                var asm = Assembly.LoadFrom(file);
                Regist(asm, item);
            }
            TypeInfoCache.BuildUpDependency();
        }
        
        private void Regist(Type type, TypeElement te, Thallo.Configuration.AssemblyElement ae) {

            if (!type.IsInterface) {
                Loger.TraceInformation("Thallo\t iron regist type " + type.FullName);
                return;
            }
            if (TypeInfoCache.TypeInfos.ContainsKey(type.TypeHandle))
                return;

            _container.RegisterType(type,
                  new InterceptionBehavior<OperationPolicyInjectionBehavior>(),
                  new Interceptor<TransparentProxyInterceptor>());

            Loger.TraceInformation("Thallo\t regist type " + type.FullName);

            var info = Create(type, te, ae);

            TypeInfoCache.TypeInfos.Add(type.TypeHandle, info);
        }

        #endregion
    }
}
